package com.syl.starter.itools.common.utils;

import com.syl.starter.itools.base.exception.ReflectException;
import com.syl.starter.itools.base.utils.LogUtils;
import com.syl.starter.itools.common.annotations.MethodAlias;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * 反射基础工具类
 * 相关注解 @MethodAlias 当获取方法或调用方法时存在重载方法时需要使用
 * @author syl
 * @create 2018-06-17 0:53
 **/
public class ReflectUtils {
    private static final String SET = "set";
    private static final String IS = "is";
    private static final String GET = "get";
    private static final String CLASS_FOR_NAME_CACHE_PREFIX = "class_for_cache_data_key_";
    private static Logger LOG = LoggerFactory.getLogger(ReflectUtils.class);
    private static Map<String,Class> BASE_DATA_TYPE_MAP = new HashMap<>(10);
    static {
        BASE_DATA_TYPE_MAP.put("byte", byte.class);
        BASE_DATA_TYPE_MAP.put("short", short.class);
        BASE_DATA_TYPE_MAP.put("int", int.class);
        BASE_DATA_TYPE_MAP.put("long", long.class);
        BASE_DATA_TYPE_MAP.put("float", float.class);
        BASE_DATA_TYPE_MAP.put("double", double.class);
        BASE_DATA_TYPE_MAP.put("boolean", boolean.class);
        BASE_DATA_TYPE_MAP.put("char", char.class);
    }

    /**
     *
     * 利用缓存优化Class.forName  获取class
     * @param className 例如 com.syl.framework.commontools.ReflectUtils
     *
     * @return
     */
    public static Class<?> getClass(String className) throws ClassNotFoundException {
        String cacheKey =  CLASS_FOR_NAME_CACHE_PREFIX + className;
        if(SimpleCacheUtils.isExist(cacheKey)){
            return SimpleCacheUtils.getData(cacheKey);
        }else {
            Class<?> aClass = Class.forName(className);// 缓存该class 反射查找class很耗费性能
            SimpleCacheUtils.setData(cacheKey, aClass);
            return aClass;
        }
    }

    /**
     * 实例化对象 无参
     * @param cz class
     * @param <T>
     * @return
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public static <T> T instantiation(Class<T> cz) throws IllegalAccessException, InstantiationException {
        Object object = cz.newInstance();
        return cz.cast(object);
    }

    /**
     * 实例化对象 带参数
     * @param cz
     * @param paramType 参数类型
     * @param paramValues 参数值
     * @param <T>
     *
     * @return
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     */
    public static <T> T instantiation(Class<T> cz, Class[] paramType, Object... paramValues) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Object object = cz.getConstructor(paramType).newInstance(paramValues);
        return cz.cast(object);
    }

    /**
     * 获取所有private 字段
     * @param cz
     * @return
     */
    public static Field[] getPrivateField(Class cz){
        Field[] declaredFields = cz.getDeclaredFields();
        return Arrays.stream(declaredFields).filter(field ->
                (!Modifier.isStatic(field.getModifiers()))
        ).toArray(Field[]:: new);
    }

    /**
     * 得到目标方法的参数值类型
     * 当目标方法包含有同名重载方法时 需要使用@Alias注解设置方法别名
     * @param cz 目标Class
     * @param methodName 方法名称
     * @param alias 方法别名 目标方法无重载方法时可为null 否则必填
     * @return 返回该方法的所有参数类型 除非传入参数为空返回null
     */
    public static Class<?>[] getMethodParamType(Class cz, String methodName, String alias){
        ArrayList<Class> list = new ArrayList<>(10);
        if(cz == null || StringUtils.isEmpty(methodName))
            return null;
        Method[] methods = cz.getMethods();
        for (Method method : methods) {
            String name = method.getName();
            if(!StringUtils.equals(name, methodName))
                continue;
            MethodAlias annotation = method.getAnnotation(MethodAlias.class);
            if(alias == null && annotation == null) { //注解和别名都为空 判定成立
                addParameter(method, list);
                break;
            }
            if(annotation == null) // 别名不为空 注解为空不是一个方法
                continue;
            String value = annotation.value();
            if(StringUtils.equals(value, alias)){ // 注解和别名相同 退出循环
                addParameter(method, list);
                break;
            }
        }
        return list.toArray(new Class[]{});
    }

    /**
     * 根据方法名获取方法对象 只能获取无重载方法的方法
     * @param cz
     * @param methodName
     * @return
     */
    public static Method getMethod(Class cz,String methodName){
        return getMethod(cz, methodName, (String)null);
    }

    /**
     * 依据方法名和方法别名  获取目标方法的 Method 对象
     * @param cz 目标Class
     * @param methodName 方法名称
     * @param alias 方法别名 目标方法无重载方法时可为null 否则必填
     * @return 返回该方法的Method  除非传入参数为空返回null
     */
    public static Method getMethod(Class cz,String methodName,String alias){
        Class<?>[] paramType = getMethodParamType(cz, methodName, alias);
        return getMethod(cz, methodName, paramType);
    }

    /**
     * 依据方法名和方法别名  验证方法值个数,不足返回null
     * @param cz
     * @param methodName 方法名
     * @param alias 方法别名
     * @param paramValues 参数值
     * @return
     */
    public static Method getMethodCheck(Class cz,String methodName, String alias, Object... paramValues){
        Class<?>[] paramType = getMethodParamType(cz, methodName, alias);
        if(paramValues == null || paramType == null)
            return null;
        if (paramType.length != paramValues.length)
            return null;
        return getMethod(cz, methodName, paramType);
    }

    /**
     * 依据方法名和方法参数类型顺序  获取目标方法的 Method 对象
     * @param cz 目标Class
     * @param methodName 方法名称
     * @param paramTypes 方法参数类型
     * @return
     */
    public static Method getMethod(Class cz, String methodName, Class... paramTypes){
        if(cz == null || StringUtils.isEmpty(methodName))
            return null;
        if(paramTypes == null)
            return null;
        try {
            return cz.getMethod(methodName, paramTypes);
        } catch (NoSuchMethodException e) {
            LogUtils.error(LOG, "方法不存在 或 方法类型不匹配 {}", e, methodName);
            for (Class<?> paramType : paramTypes) {
                LOG.error("获取到的方法类型为  {}", paramType.getTypeName());
            }
        }
        return null;
    }

    /**
     * 根据方法名和参数调用静态方法或枚举类方法
     * @param target 目标class
     * @param methodName 方法名称
     * @param paramValue 参数值
     * @param returnCZ 返还值类型 可以为空 永远返回空
     * @param <T>
     * @return
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public static <T> T methodInvoke(Class target,String methodName, Object[] paramValue, Class<T> returnCZ) throws InvocationTargetException, IllegalAccessException {
        Method method = getMethod(target, methodName);
        Object invoke = method.invoke(null, paramValue);
        return helpReturn(invoke, returnCZ);
    }

    /**
     * 依据方法名 调用无参目标方法 (不支持重载)
     * @param object 目标对象
     * @param methodName 方法名称
     * @param returnCZ 返回类型 可以为空 永远返回空
     * @return
     */
    public static <T> T methodInvoke(Object object, String methodName, Class<T> returnCZ) throws ReflectException {
        return methodInvoke(object, methodName,null, returnCZ);
    }

    /**
     * 依据方法名 调用目标方法 (不支持重载)
     * @param object 目标对象
     * @param methodName 方法名称
     * @param paramValue 调用参数值 个数和顺序必须和方法参数一致
     *
     * @throws ReflectException
     */
    public static void methodInvoke(Object object, String methodName, Object[] paramValue) throws ReflectException {
        methodInvoke(object, methodName, paramValue, null);
    }

    /**
     * 依据方法名 调用目标方法 (不支持重载)
     * @param object 目标对象
     * @param methodName 方法名称
     * @param paramValue 调用参数值 个数和顺序必须和方法参数一致
     * @param returnCZ 返回类型 可以为空 永远返回空
     * @return
     */
    public static <T> T methodInvoke(Object object, String methodName, Object[] paramValue, Class<T> returnCZ) throws ReflectException {
        return methodInvoke(object, methodName,null, paramValue, returnCZ);
    }

    /**
     * 依据方法名和别名调用目标方法
     * @param object 目标对象
     * @param methodName 方法名称
     * @param alias 方法别名
     * @param paramValue 调用参数值 个数和顺序必须和方法参数一致
     * @param returnCZ 返回类型 可以为空 永远返回空
     * @return
     */
    public static <T> T methodInvoke(Object object, String methodName, String alias, Object[] paramValue, Class<T> returnCZ) throws ReflectException {
        if(object == null)
            return null;
        Class cz = object.getClass();
        Class<?>[] paramType = getMethodParamType(cz, methodName, alias);
        if(paramValue != null  && paramType.length != paramValue.length){
            throw new ReflectException("方法参数个数和传入参数值的个数不一致 请检查paramValue个数 "+ methodName);
        }
        if(paramValue == null && paramType != null && paramType.length > 0){
            throw new ReflectException("方法参数个数和传入参数值的个数不一致 请检查paramValue个数 "+ methodName);
        }
        Method method = getMethod(cz, methodName, paramType);
        try {
            Object invoke = method.invoke(object, paramValue);
            return helpReturn(invoke, returnCZ);
        } catch (IllegalAccessException e) {
            LogUtils.error(LOG, "反射调用方法失败 {}", e, methodName);
        } catch (InvocationTargetException e) {
            LogUtils.error(LOG, "反射调用方法失败 {}",e, methodName);
        }
        return null;
    }

    /**
     * 通用给set方法赋值
     * @param entity 实例对象
     * @param field 字段
     * @param fieldValue 字段值
     */
    public static void setFieldValue(Object entity, String field, Object... fieldValue) throws ReflectException {
        setFieldValue(entity,null,field, fieldValue);
    }

    /**
     * 通用给set方法赋值
     * @param entity 实例对象
     * @param prefix 方法名前缀
     * @param field 字段
     * @param fieldValue 字段值
     * @return
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public static void setFieldValue(Object entity, String prefix, String field, Object[] fieldValue) throws ReflectException {
        field = StringUtils.trim(field);
        if(StringUtils.isEmpty(field))
            return;
        String prefixTemp = StringUtils.isEmpty(prefix) ? SET : prefix;
        String setMethodName = prefixTemp + StringUtils.firstCharUpper(field);
        methodInvoke(entity, setMethodName, fieldValue,null);
    }

    public static Object getFieldValue(Object entity, String field) throws ReflectException {
        return getFieldValue(entity, field, Object.class);
    }

    public static <T> T getFieldValue(Object entity, String field, Class<T> cz) throws ReflectException {
        return getFieldValue(entity, null, field, cz);
    }

    /**
     * 通用获取java bean 对象字段
     * @param entity
     * @param field
     * @param getPrefix get 方法前缀
     * @param cz 返回的类型class
     * @return get的值 get失败返回null
     */
    public static <T> T getFieldValue(Object entity, String getPrefix, String field, Class<T> cz) throws ReflectException {
        if(entity == null)
            return null;
        field = StringUtils.trim(field);
        if(StringUtils.isEmpty(field))
            return null;
        String baseName = StringUtils.firstCharUpper(field);
        String getMethodName;
        if(StringUtils.isNotEmpty(getPrefix)){
            return methodInvoke(entity, getPrefix + baseName, cz);
        }
        if(cz == boolean.class) {
            getMethodName = IS + baseName;
        }
        else
            getMethodName = GET + baseName;
        return methodInvoke(entity, getMethodName, cz);
    }

    /**
     * 添加参数
     * @param method
     * @param list
     */
    private static void addParameter(Method method, ArrayList<Class> list){
        for (Parameter p : method.getParameters()) {
            String className = null;
            try {
                className = p.getType().getName();
                Class baseClass = BASE_DATA_TYPE_MAP.get(className);
                list.add(baseClass == null ? ReflectUtils.getClass(className) : baseClass);
            } catch (ClassNotFoundException e) {
                LogUtils.error(LOG, "类或不存在 class path {}", e, className);
            }
        }
    }


    private static <T> T helpReturn(Object invoke, Class<T> returnCZ){
        if(invoke == null)
            return null;
        if(returnCZ == null || BASE_DATA_TYPE_MAP.containsValue(returnCZ))
            return (T) invoke;
        return returnCZ.cast(invoke);
    }

}
